
#pragma once
#include <iostream>
#include "log.hpp"
#include <strings.h>
#include <string.h>
#include <functional>
//创建套接字
#include <sys/types.h>
#include <sys/socket.h>

//bind struct
#include <netinet/in.h>

#include <arpa/inet.h>
using namespace std;
//using func_t = function<string(const string&)>;
typedef function<string(const string&)> func_t;

extern Log log;
enum{
    SOCKET_ERR=1,
    BIND_ERR
};

uint16_t defaultport = 8080;
string defaultip = "0.0.0.0";

const int size = 1024;
class UdpServer
{
public:
    UdpServer(const uint16_t& port = defaultport, const string& ip = defaultip)
        :port_(port)
        ,ip_(ip)
        ,sockfd_(0)
        ,isrunning_(false)
    { }
    void Init()
    {
        cout << "ServerInit" << endl;
        //类比文件接口
        //int socket(int domain, int type, int protocol);
        /// 1
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
        //使用ipv4协议、面向用户数据报、协议段为0

        if(sockfd_ < 0)
        {
            log(Fatal, "socket create err, sockfd: %d",sockfd_);
            exit(SOCKET_ERR);
        }

        log(Info, "socket create success, sockfd: %d", sockfd_);//因为套接字文件也是文件，所以从3开始（0，1，2被占）

        ///2.告知端口号 、ip(初始化定义结构体)
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);//因为要在网络中互相发送，所以要保证该端口号是网络字节序列
        //如何port转化为网络字节序？
        // uint16_t htons(uint16_t hostshort);
        local.sin_addr.s_addr = inet_addr(ip_.c_str());
        //local.sin_addr.s_addr = INADDR_ANY;
        //in_addr_t inet_addr(const char *cp);字符串转为网络字节序
        //如何将<整数ip> <==> <字符串ip>
        //stoi 、 to_string

        ///3.绑定 bind
        //local结构体对象定义在哪里？（用户区 栈）
        //将local与内核-网络套接字关联起来
        //int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
        //bind参数：sockaddr 与 sockaddr_in,强制类型转换
        int n = bind(sockfd_, (const struct sockaddr*)&local, sizeof(local));
        if(n < 0)
        {
            log(Fatal, "bind error, error: %d, errno string: %s",errno,strerror(errno));
            exit(BIND_ERR);
        }
        log(Info,"bind success");


    }
    void Run(func_t func)
    {
        cout << "ServerRun" << endl;

        //服务器必须死循环
        isrunning_ = true;
        char inbuffer[size];
        while(true)
        {
            //从指定ucp获取报文
            //ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
            //                  struct sockaddr *src_addr, socklen_t *addrlen);
            //                    输出型参数（谁发送的报文）    缓冲区长度
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);
            if(n < 0)
            {
                log(Warning, "recvfrom err, error: %d, err string: &s",errno,strerror(errno));
                continue;
            }

            uint16_t port = ntohs(client.sin_port);//得到客户端的端口号
            string ip = inet_ntoa(client.sin_addr);
            inbuffer[n] = 0;
            string info = inbuffer;
       
            //数据处理
            string echo_string = func(info);

            //处理完毕后再发送给用户
            //ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
            //              const struct sockaddr *dest_addr, socklen_t addrlen);
            sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (const struct sockaddr*)&client, len);
        }
    }
    ~UdpServer()
    {
         if(sockfd_ > 0)
         {
            close(sockfd_);//实际上返回后进程结束自动会关闭文件描述符
         }
    }

private:
    int sockfd_;
    uint16_t port_;//端口号
    string ip_; // ip

    bool isrunning_;
};